package pack;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;

public class Memory {
	public ArrayList<Integer> memory;

	private static final int MEMORYSIZE = 65536; // 2^16
	private HashMap<String, Integer> leftLabels;
	private HashMap<String, ArrayList<Integer>> rightLabels;
	private int lineCode, increment, index;
	private Scanner scan;
	private String value;

	private HashMap<String, Integer> instructionCode;

	public Memory() {
		rightLabels = new HashMap<String, ArrayList<Integer>>();
		leftLabels = new HashMap<String, Integer>();
		
		memory = new ArrayList<Integer>();
		for (int i = 0; i < MEMORYSIZE; i++) {
			memory.add(0x0000);
		}
		index = 0x0000;

		instructionCode = new HashMap<>();

		instructionCode.put("ADD", 0x0000);
		instructionCode.put("SUB", 0x0100);
		instructionCode.put("MOV", 0x0200);
		instructionCode.put("CMP", 0x0300);
		instructionCode.put("AND", 0x0400);
		instructionCode.put("OR", 0x0500);

		instructionCode.put("NOT", 0x0600);
		instructionCode.put("CLR", 0x0700);
		instructionCode.put("NEG", 0x0800);
		instructionCode.put("SHL", 0x0900);
		instructionCode.put("SHR", 0x0A00);

		instructionCode.put("BRZ", 0x0B00);
		instructionCode.put("BRN", 0x0C00);
		instructionCode.put("BRE", 0x0D00);
		instructionCode.put("BRL", 0x0E00);
		instructionCode.put("BRG", 0x0F00);
		instructionCode.put("BRC", 0x1000);
		instructionCode.put("JMP", 0x1100);

		instructionCode.put("HALT", 0x1200);
	}

	public void init(String filePath) {
		readProgram(filePath);
		changeLabelToAddress();
	}

	private void readProgram(final String filePath) {
		try {
			scan = new Scanner(new File(filePath));
			while (scan.hasNext()) {
				value = scan.next();
				increment = 0x0000;

				handleInstructionCode(value);

				// Adiciona na memoria o codigo lido
				memory.set(index, lineCode);
				index += increment + 1;
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public String toString() {
		String s = "";
		int i=0;
		//imprime a memória até encontrar 0x1200
		while(memory.get(i)!=0x1200) {
			s += "["+i+"]  "+ Integer.toHexString(memory.get(i)) + "\n";
			i++;
		}
		s += "["+i+"]  1200\n";
		
		
		return s;
	}

	public void clear() {
		memory.clear();
		for (int i = 0; i < MEMORYSIZE; i++) {
			memory.set(i,new Integer(0));
		}
	}

	// Traduz a instrução lida para código
	private void handleInstructionCode(String value) {
		switch (value) {
		case "ADD":
		case "SUB":
		case "MOV":
		case "CMP":
		case "AND":
		case "OR":
			lineCode = instructionCode.get(value);
			value = scan.next();

			// esse shift é para deslocar o codigo do primeiro termo
			// para a esquerda, a fim de dar espaço para o segundo.
			lineCode += getOpCode(value) << 4;
			value = scan.next(); // Pula a virgula
			value = scan.next();
			lineCode += getOpCode(value);

			break;
		case "NOT":
		case "CLR":
		case "NEG":
		case "SHL":
		case "SHR":
			lineCode = instructionCode.get(value);
			value = scan.next();
			lineCode += getOpCode(value);

			break;
		case "BRZ":
		case "BRN":
		case "BRE":
		case "BRL":
		case "BRG":
		case "BRC":
		case "JMP":
			lineCode = instructionCode.get(value);
			value = scan.next();
			value = removeBrackets(value);
			increment++;
			addRightLabel(value);

			break;
		case "HALT":
			lineCode = 0x1200;

			break;
		default:
			// LEFT LABEL
			value = removeBrackets(value);
			addLeftLabel(value);

			// pega o codigo referenciado pelo label
			value = scan.next();
			handleInstructionCode(value);

			break;
		}
	}

	// Retorna o código do operador lido
	private int getOpCode(String value) {
		value = removeBrackets(value);

		if (value.equals("R0"))
			return 0x0000;
		else if (value.equals("R1"))
			return 0x0001;
		else if (value.equals("R2"))
			return 0x0002;
		else if (value.equals("R3"))
			return 0x0003;
		else if (value.equals("R4"))
			return 0x0004;
		else if (value.equals("R5"))
			return 0x0005;
		else if (value.equals("(R0)"))
			return 0x0006;
		else if (value.equals("(R1)"))
			return 0x0007;
		else if (value.equals("(R2)"))
			return 0x0008;
		else if (value.equals("(R3)"))
			return 0x0009;
		else if (value.equals("(R4)"))
			return 0x000A;
		else if (value.equals("(R5)"))
			return 0x000B;
		else { // Constante
			increment++;
			
			if(value.contains("h")) {
				value = value.replace("h", "");
				memory.set(index+increment, Integer.parseInt(value, 16));
			} else {
				memory.set(index+increment, Integer.parseInt(value));
			}
			
			return 0x000C;
		}
	}

	private String removeBrackets(String value) {
		// removendo "<" e ">"
		value = value.replace("<", "");
		value = value.replace(">", "");

		return value;
	}

	/**
	 * Descobre o endereço de cada label direito.
	 */
	private void changeLabelToAddress() {
		if(!rightLabels.isEmpty()) {
			Iterator<Map.Entry<String, ArrayList<Integer>>> it = rightLabels.entrySet().iterator();
			
			while (it.hasNext()) {
				Map.Entry<String, ArrayList<Integer>> pair = (Map.Entry<String, ArrayList<Integer>>) it
						.next();
				ArrayList<Integer> keys = pair.getValue();
	
				if(!keys.isEmpty()){
					for (Integer i : keys) {
						lineCode = leftLabels.get(pair.getKey());
						memory.set(i, lineCode);
						//memory.set(0x0 + i, leftLabels.get(pair.getKey()));
					}
				}
			}
		}
	}

	private void addLeftLabel(String label) {
		label = label.replace(":", "");
		leftLabels.put(label, index);
	}

	private void addRightLabel(String label) {
		// Pega toda a lista de labels que ja foram adicionados
		ArrayList<Integer> i = rightLabels.get(label);
		
		if (i == null)
			i = new ArrayList<Integer>();
			
		i.add(index+increment);
		

		// insere mais esse label
		

		rightLabels.put(label, i);
	}
}
